//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package appstore_dao

import (
	"context"
	"regexp"
	"time"

	"atomgit.com/openlinksaas/api-server/dao/db_ns"
	"atomgit.com/openlinksaas/proto-gen-go.git/appstore_api"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type BaseAppInfoDbItem struct {
	AppName    string `bson:"appName" json:"appName"`
	AppDesc    string `bson:"appDesc" json:"appDesc"`
	IconFileId string `bson:"iconFileId" json:"iconFileId"`
	SrcUrl     string `bson:"srcUrl" json:"srcUrl"`
	AppType    uint32 `bson:"appType" json:"appType"`
}

type AppNetPermDbItem struct {
	CrossDomainHttp bool `bson:"crossDomainHttp" json:"crossDomainHttp"`
	ProxyRedis      bool `bson:"proxyRedis" json:"proxyRedis"`
	ProxyMysql      bool `bson:"proxyMysql" json:"proxyMysql"`
	ProxyPostGres   bool `bson:"proxyPostGres" json:"proxyPostGres"`
	ProxyMongo      bool `bson:"proxyMongo" json:"proxyMongo"`
	ProxySsh        bool `bson:"proxySsh" json:"proxySsh"`
	NetUtil         bool `bson:"netUtil" json:"netUtil"`
	ProxyGrpc       bool `bson:"proxyGrpc" json:"proxyGrpc"`
}

type AppFsPermDbItem struct {
	ReadFile  bool `bson:"readFile" json:"readFile"`
	WriteFile bool `bson:"writeFile" json:"writeFile"`
}

type AppExtraPermDbItem struct {
	CrossOriginIsolated bool `bson:"crossOriginIsolated" json:"crossOriginIsolated"`
	OpenBrowser         bool `bson:"openBrowser" json:"openBrowser"`
}

type AppPermDbItem struct {
	NetPerm AppNetPermDbItem `bson:"netPerm" json:"netPerm"`
	// MemberPerm AppMemberPermDbItem `bson:"memberPerm"`
	// IssuePerm  AppIssuePermDbItem  `bson:"issuePerm"`
	// EventPerm  AppEventPermDbItem  `bson:"eventPerm"`
	FsPerm    AppFsPermDbItem    `bson:"fsPerm" json:"fsPerm"`
	ExtraPerm AppExtraPermDbItem `bson:"extraPerm" json:"extraPerm"`
}

type AppInfoDbItem struct {
	AppId          string            `bson:"_id" json:"_id"`
	BaseInfo       BaseAppInfoDbItem `bson:"baseInfo" json:"baseInfo"`
	MajorCateId    string            `bson:"majorCateId" json:"majorCateId"`
	MinorCateId    string            `bson:"minorCateId" json:"minorCateId"`
	SubMinorCateId string            `bson:"subMinorCateId" json:"subMinorCateId"`
	AppPerm        AppPermDbItem     `bson:"appPerm" json:"appPerm"`
	FileId         string            `bson:"fileId" json:"fileId"`
	OsWindows      bool              `bson:"osWindows" json:"osWindows"`
	OsMac          bool              `bson:"osMac" json:"osMac"`
	OsLinux        bool              `bson:"osLinux" json:"osLinux"`
	UserApp        bool              `bson:"userApp" json:"userApp"`
	CreateTime     int64             `bson:"createTime" json:"createTime"`
	UpdateTime     int64             `bson:"updateTime" json:"updateTime"`
	InstallCount   uint32            `bson:"installCount" json:"installCount"`
	AgreeCount     uint32            `bson:"agreeCount" json:"agreeCount"`
}

func (item *AppInfoDbItem) ToAppInfo() *appstore_api.AppInfo {
	return &appstore_api.AppInfo{
		AppId: item.AppId,
		BaseInfo: &appstore_api.BaseAppInfo{
			AppName:    item.BaseInfo.AppName,
			AppDesc:    item.BaseInfo.AppDesc,
			IconFileId: item.BaseInfo.IconFileId,
			SrcUrl:     item.BaseInfo.SrcUrl,
			AppType:    appstore_api.APP_TYPE(item.BaseInfo.AppType),
		},
		MajorCate: &appstore_api.MajorCate{
			CateId: item.MajorCateId,
		},
		MinorCate: &appstore_api.MinorCate{
			CateId: item.MinorCateId,
		},
		SubMinorCate: &appstore_api.SubMinorCate{
			CateId: item.SubMinorCateId,
		},
		AppPerm: &appstore_api.AppPerm{
			NetPerm: &appstore_api.AppNetPerm{
				CrossDomainHttp: item.AppPerm.NetPerm.CrossDomainHttp,
				ProxyRedis:      item.AppPerm.NetPerm.ProxyRedis,
				ProxyMysql:      item.AppPerm.NetPerm.ProxyMysql,
				ProxyPostGres:   item.AppPerm.NetPerm.ProxyPostGres,
				ProxyMongo:      item.AppPerm.NetPerm.ProxyMongo,
				ProxySsh:        item.AppPerm.NetPerm.ProxySsh,
				NetUtil:         item.AppPerm.NetPerm.NetUtil,
				ProxyGrpc:       item.AppPerm.NetPerm.ProxyGrpc,
			},
			FsPerm: &appstore_api.AppFsPerm{
				ReadFile:  item.AppPerm.FsPerm.ReadFile,
				WriteFile: item.AppPerm.FsPerm.WriteFile,
			},
			ExtraPerm: &appstore_api.AppExtraPerm{
				CrossOriginIsolated: item.AppPerm.ExtraPerm.CrossOriginIsolated,
				OpenBrowser:         item.AppPerm.ExtraPerm.OpenBrowser,
			},
		},
		FileId:       item.FileId,
		OsWindows:    item.OsWindows,
		OsMac:        item.OsMac,
		OsLinux:      item.OsLinux,
		UserApp:      item.UserApp,
		CreateTime:   item.CreateTime,
		UpdateTime:   item.UpdateTime,
		InstallCount: item.InstallCount,
		AgreeCount:   item.AgreeCount,
	}
}

type _AppDao struct {
	mc *mongo.Collection
}

func newAppDao(db *mongo.Database) (*_AppDao, error) {
	mc := db.Collection(db_ns.DB_NS_APP_STORE + "app")
	_, err := mc.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
		{
			Keys: bson.D{{"updateTime", 1}},
		},
		{
			Keys: bson.D{{"baseInfo.appType", 1}},
		},
	})
	if err != nil {
		return nil, err
	}
	return &_AppDao{mc: mc}, nil
}

func (db *_AppDao) Insert(ctx context.Context, dbItem *AppInfoDbItem) error {
	_, err := db.mc.InsertOne(ctx, dbItem)
	return err
}

func (db *_AppDao) Count(ctx context.Context, listParam *appstore_api.ListAppParam) (uint32, error) {
	query := db.genQueryFromListParam(listParam)
	count, err := db.mc.CountDocuments(ctx, query)
	return uint32(count), err
}

func (db *_AppDao) genQueryFromListParam(listParam *appstore_api.ListAppParam) bson.M {
	query := bson.M{}
	if listParam.FilterByMajorCateId {
		query["majorCateId"] = listParam.MajorCateId
	}
	if listParam.FilterByMinorCateId {
		query["minorCateId"] = listParam.MinorCateId
	}
	if listParam.FilterBySubMinorCateId {
		query["subMinorCateId"] = listParam.SubMinorCateId
	}
	if listParam.FilterByOsScope {
		if listParam.OsScope == appstore_api.OS_SCOPE_OS_SCOPE_WINDOWS {
			query["osWindows"] = true
		} else if listParam.OsScope == appstore_api.OS_SCOPE_OS_SCOPE_MAC {
			query["osMac"] = true
		} else if listParam.OsScope == appstore_api.OS_SCOPE_OS_SCOPE_LINUX {
			query["osLinux"] = true
		}
	}
	if listParam.FilterByKeyword {
		query["baseInfo.appName"] = primitive.Regex{
			Pattern: regexp.QuoteMeta(listParam.Keyword),
			Options: "i",
		}
	}
	return query
}

func (db *_AppDao) List(ctx context.Context, listParam *appstore_api.ListAppParam, offset, limit int64, sortKey appstore_api.SORT_KEY) ([]*AppInfoDbItem, error) {
	sortBy := bson.D{{"updateTime", -1}}
	if sortKey == appstore_api.SORT_KEY_SORT_KEY_INSTALL_COUNT {
		sortBy = bson.D{{"installCount", -1}, {"updateTime", -1}}
	} else if sortKey == appstore_api.SORT_KEY_SORT_KEY_AGREE_COUNT {
		sortBy = bson.D{{"agreeCount", -1}, {"updateTime", -1}}
	}
	query := db.genQueryFromListParam(listParam)
	cursor, err := db.mc.Find(ctx, query, options.Find().SetSkip(offset).SetLimit(limit).SetSort(sortBy))
	if err != nil {
		return nil, err
	}
	retList := []*AppInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_AppDao) ListAll(ctx context.Context) ([]*AppInfoDbItem, error) {
	cursor, err := db.mc.Find(ctx, bson.M{})
	if err != nil {
		return nil, err
	}
	retList := []*AppInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_AppDao) ListById(ctx context.Context, appIdList []string) ([]*AppInfoDbItem, error) {
	if len(appIdList) == 0 {
		return []*AppInfoDbItem{}, nil
	}
	query := bson.M{"_id": bson.M{"$in": appIdList}}
	cursor, err := db.mc.Find(ctx, query)
	if err != nil {
		return nil, err
	}
	retList := []*AppInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_AppDao) ListByType(ctx context.Context, appType uint32) ([]*AppInfoDbItem, error) {
	query := bson.M{"baseInfo.appType": appType}
	cursor, err := db.mc.Find(ctx, query)
	if err != nil {
		return nil, err
	}
	retList := []*AppInfoDbItem{}
	err = cursor.All(ctx, &retList)
	return retList, err
}

func (db *_AppDao) Get(ctx context.Context, appId string) (*AppInfoDbItem, error) {
	retItem := &AppInfoDbItem{}
	err := db.mc.FindOne(ctx, bson.M{"_id": appId}).Decode(retItem)
	return retItem, err
}

func (db *_AppDao) Exist(ctx context.Context, appId string) (bool, error) {
	count, err := db.mc.CountDocuments(ctx, bson.M{"_id": appId})
	return count > 0, err
}

func (db *_AppDao) UpdateBaseInfo(ctx context.Context, appId string, dbItem *BaseAppInfoDbItem) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"baseInfo": dbItem, "updateTime": time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) UpdateAppFile(ctx context.Context, appId, fileId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"fileId": fileId, "updateTime": time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) UpdateCate(ctx context.Context, appId, majorCateId, minorCateId, subMinorCateId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"majorCateId": majorCateId, "minorCateId": minorCateId,
			"subMinorCateId": subMinorCateId,
			"updateTime":     time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) UpdateAppPerm(ctx context.Context, appId string, dbItem *AppPermDbItem) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"appPerm": dbItem, "updateTime": time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) UpdateAppOs(ctx context.Context, appId string, osWindows, osMac, osLinux bool) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"osWindows": osWindows, "osMac": osMac, "osLinux": osLinux,
			"updateTime": time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) UpdateAppScope(ctx context.Context, appId string, userApp, projectApp bool) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId},
		bson.M{"$set": bson.M{"userApp": userApp, "projectApp": projectApp,
			"updateTime": time.Now().UnixNano() / 1e6}})
	return err
}

func (db *_AppDao) Remove(ctx context.Context, appId string) error {
	_, err := db.mc.DeleteOne(ctx, bson.M{"_id": appId})
	return err
}

func (db *_AppDao) InitAllCount(ctx context.Context) error {
	_, err := db.mc.UpdateMany(ctx, bson.M{"installCount": bson.M{"$exists": false}}, bson.M{"$set": bson.M{
		"installCount": 0,
		"agreeCount":   0,
	}})
	return err
}

func (db *_AppDao) IncInstallCount(ctx context.Context, appId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId}, bson.M{"$inc": bson.M{"installCount": 1}})
	return err
}

func (db *_AppDao) IncAgreeCount(ctx context.Context, appId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId}, bson.M{"$inc": bson.M{"agreeCount": 1}})
	return err
}

func (db *_AppDao) DecAgreeCount(ctx context.Context, appId string) error {
	_, err := db.mc.UpdateOne(ctx, bson.M{"_id": appId, "agreeCount": bson.M{"$gt": 0}}, bson.M{"$inc": bson.M{"agreeCount": -1}})
	return err
}
